﻿//Контрол автодополнения
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Eco.ObjectRepresentation;

using CommandGraph.Model;
using CommonClasses.Utility;
using Descriptions_DomainModel.Model;


namespace Plugins.IntelliSense
{
    /// <summary>
    /// Многострочный основанный на TextBox'e контрол позволяет вводить LPMED-выражения
    /// и выполняет автокомплит известных ему идентификаторов.
    /// </summary>
    [ToolboxBitmap(typeof(IntelliSenseControl))]
    public partial class IntelliSenseControl : UserControl
    {
        #region Private fields

        /// <summary>
        /// Используется при Multilined TextBox
        /// </summary>
        private const int TextBoxOneLineHeight = 10;
        /// <summary>
        /// Модель стенда, тут объявлены идентификаторы, для которых будет возможен автокомплит
        /// </summary>
        private DomainModel domainModel;
        /// <summary>
        /// Идентификаторы переменных (их нет в DomainModel, поэтому передаются отдельно)
        /// </summary>
        private IList<string> variables;
        /// <summary>
        /// Ссылка на EcoSpace
        /// </summary>
        private IEcoServiceProvider serviceProvider;
        /// <summary>
        /// Сервис поиска автодоплнений
        /// </summary>
        private IHIntelliSenseProvider provider;
        /// <summary>
        /// Внутренний контрол - выпадающий список, с вариантами дополнений
        /// </summary>
        private DropDownList downlist;
        /// <summary>
        /// Разделители, которые вызывают срабатывание автокомплит 
        /// Плюс автокомплит всегда срабатывает при нажатии CTRL+Space
        /// </summary>
        private IList<string> activeDelim = new List<string>() { "." };
        /// <summary>
        /// Разделители идентификаторов
        /// </summary>
        private IList<string> identDelims = new List<string>() { ".", 
            " ", "\n", "\r\n", "(", ")", "+", "-", "=", "/", ";"};
        
        #endregion

        #region Public properties
        
        /// <summary>
        /// Программный ввод текста в контрол (автодополнение не вызывается)
        /// </summary>
        public override string Text
        {
            get { return textBox1.Text; }
            set { textBox1.Text = value; }
        }

        #endregion

        #region Public methods
        /// <summary>
        /// Инициализация внутренних контролов - текст бокса и выпадающего списка
        /// </summary>
        public IntelliSenseControl()
        {
            InitializeComponent();
            downlist = new DropDownList();
            downlist.Visible=false;
            downlist.VisibleChanged += new EventHandler(Downlist_VisibleChanged);
            textBox1.Multiline = true;
        }

        /// <summary>
        /// Инициализация параметров автокомпита
        /// </summary>
        /// <param name="serviceProvider">Ссылка на EcoSpace</param>
        /// <param name="domainModel">Модель стенда, тут объявлены идентификаторы, для которых будет возможен автокомплит</param>
        /// <param name="dropDownListParent">Контрол на котором будет отображаться выпадающий список (обычно главная форма)</param>
        /// <param name="variables">Идентификаторы переменных (их нет в DomainModel, поэтому передаются отдельно)</param>
        /// <param name="identDelims">Разделители идентификаторов</param>
        /// <param name="activeDelim">Разделители, которые вызывают срабатывание автокомплит 
        /// Плюс автокомплит всегда срабатывает при нажатии CTRL+Space</param>
        public void Initialize(
                               IEcoServiceProvider serviceProvider,
                               Descriptions_DomainModel.Model.DomainModel domainModel,
                               Control dropDownListParent,
                               IList<string> variables,
                               IList<string> identDelims,
                               IList<string> activeDelim)
        {
            dropDownListParent.Controls.Add(downlist);

            this.serviceProvider = serviceProvider;
            this.provider = new IHIntelliSenseProvider(serviceProvider);
            this.domainModel = domainModel;

            this.variables = variables;
            this.activeDelim = activeDelim;
            this.identDelims = identDelims;
        }
        /// <summary>
        /// Инициализация параметров автокомпита
        /// </summary>
        /// <param name="serviceProvider">Ссылка на EcoSpace</param>
        /// <param name="domainModel">Модель стенда, тут объявлены идентификаторы, для которых будет возможен автокомплит</param>
        /// <param name="dropDownListParent">Контрол на котором будет отображаться выпадающий список (обычно главная форма)</param>
        /// <param name="variables">Идентификаторы переменных (их нет в DomainModel, поэтому передаются отдельно)</param>
        public void Initialize(
                              IEcoServiceProvider serviceProvider,
                              Descriptions_DomainModel.Model.DomainModel domainModel,
                              Control dropDownListParent,
                              IList<string> variables)
        {
            dropDownListParent.Controls.Add(downlist);

            this.domainModel = domainModel;
            this.variables = variables;
            this.serviceProvider = serviceProvider;
            this.provider = new IHIntelliSenseProvider(serviceProvider);
        }


        
        
        #endregion

        #region Events handlers

        /// <summary>
        /// Показываем выпадающий список при нажатии на CTRL+Space
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void TextBox1_KeyDown(object sender, KeyEventArgs e)
        {
            if(e.Control && (e.KeyCode == Keys.Space))
            {
                FillDropDownList();
                ShowDropDownListNearCursor();
                e.SuppressKeyPress = true;
            }
        }
        /// <summary>
        /// Показываем выпадающий список при появлении разделителя из списка активных разделителей
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void TextBox1_TextChanged(object sender, EventArgs e)
        {
            if (textBox1.SelectionStart > 0)
            {
                if (activeDelim.Contains(textBox1.Text[textBox1.SelectionStart - 1].ToString()))
                {
                    FillDropDownList();
                    ShowDropDownListNearCursor();
                }
            }
        }
        /// <summary>
        /// Вызываем физическое изменение строки в текст боксе, если пользователь выбрал один из вариантов в выпадающем списке
        /// Возвращаем фокус текст боксу
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void Downlist_VisibleChanged(object sender, EventArgs e)
        {
            if (downlist.Visible == false)
            {
                if (downlist.SelectedIndex >= 0)
                {
                    textBox1.Focus();
                    textBox1.SelectionStart = DoAutoComplete();
                    downlist.SelectedIndex = -1;
                }
                else
                {
                    //запомним  позицию курсора
                    int temp = textBox1.SelectionStart;
                    textBox1.Focus();
                    //восстановим  позицию курсора
                    textBox1.SelectionStart = temp;
                }
            }
        }

        #endregion

        #region Work with DropDownList 
        /// <summary>
        /// Заполняем выпадающий список, с помощью сервиса автокомплита
        /// </summary>
        private void FillDropDownList()
        {
            if (provider != null)
            {
                downlist.Items.Clear();
                downlist.Items.AddRange(
                               provider.GetCompleteList(
                                        textBox1.Text,
                                        textBox1.Text.IndexToCoordinates(textBox1.SelectionStart),
                                        variables, 
                                        domainModel).ToArray());
            }
        }
        /// <summary>
        /// "Рисуем" выпадающий список в нужном месте и передаем ему фокус
        /// </summary>
        private void ShowDropDownListNearCursor()
        {
            
            Point p;
            GetCaretPos(out p);
            downlist.Location = new Point(p.X + Location.X+5, p.Y + (int)(TextBoxOneLineHeight - 1) + Location.Y);
            downlist.BringToFront();
            downlist.Show();

            downlist.Focus();
            if (downlist.Items.Count > 0)
                downlist.SelectedIndex = 0;
        }
        #endregion

        #region Private methods
        ///TODO: можно внести эту функцию в IHIntelliSenseProvider и реализовать декларативно
        /// <summary>
        /// Выполняет изменение строки в текст боксе для автокомплита
        /// </summary>
        /// <returns>Позиция курсора после дополненного идетификатора</returns>
        private int DoAutoComplete()
        {
            //Пользователь что-то ввел и затем вызвал автокомплит и выбрал какой-то вариант,
            //мы должны заменить то, что он уже ввел, на то что выбрал:

            string fromlist = downlist.SelectedItem.ToString();
            string text = textBox1.Text;
            int pos = textBox1.SelectionStart;

            // исправление issue 111 (041009): введен отдельный спец. случай для ситуации, когда пользователь
            // выделяет что-либо реально и заменяет выделенное на автокомлит, а не просто вызывает автокомлит в месте расположения курсора
            // пользователь выделил что-то и хочет это заменить на результат автокомплита
            if (textBox1.SelectionLength > 0)
            {
                //заменяем
                text = text.Remove(textBox1.SelectionStart, textBox1.SelectionLength);
                text = text.Insert(textBox1.SelectionStart, fromlist);
            }
            // пользователь ничего не выделил, а вызвал автокомплит в месте расположения курсора
            else
                //если пользователь ничего не ввел, то и заменять нечего
                if (text == string.Empty)
                {
                    text = fromlist;
                }
                else
                {
                    //исправление issue 111 (081109): теперь pos не выходит за левую границу массива
                    //ищем, что нужно заменить в строке на подсказку автокомлита 
                    do
                        pos=pos-1 < 0 ? pos: pos-1;
                    while ((pos > 0) && (identDelims.Contains(text[pos].ToString()) == false));

                    

                    //смещаемся на символ после найденного пробела или точки
                    if (identDelims.Contains(text[pos].ToString()) == true)
                        pos++;

                    //заменяем
                    text = text.Remove(pos, textBox1.SelectionStart - pos);
                    text = text.Insert(pos, fromlist);
                }
            textBox1.Text = text;
            //возвращаем новую позицию курсора с учетом замены
            return pos + fromlist.Length+1;
        }
        #endregion

        //Определяет положение каретки в активном текст боксе
        [System.Runtime.InteropServices.DllImport("User32.dll")]
        private static extern bool GetCaretPos(out Point caretPos);
    }
}

